//!
/************************************************************************************************************
 * @file RBRenderPassDebug.cpp
 ************************************************************************************************************/

/************************************************************************************************************
 * Precompiled Header
 ************************************************************************************************************/
#include "RBMainPCH.h"
 
/************************************************************************************************************
 * Includes
 ************************************************************************************************************/
#include "RBRenderPassDebug.h"

#include "RBCamera.h"
#include "RBEntity.h"
#include "RBGeometry.h"
#include "RBHandle.h"
#include "RBHardwareBufferManager.h"
#include "RBMathVector.h"
#include "RBPlane.h"
#include "RBVertexElement.h"

#include "RBRenderProcess/RBEffectFactory.h"
#include "RBRenderProcess/RBEffect.h"
#include "RBRenderProcess/RBEffectTechnique.h"

/************************************************************************************************************
 * Defines
 ************************************************************************************************************/

/************************************************************************************************************
 * Implementation
 ************************************************************************************************************/

namespace RBRenderPassDebug
{

struct Vertex3D 
{
    float3 Pos;
    uint8 Color[4];
};


struct DebugData
{
public:

    DebugData(RBRenderer* pRenderer);
   ~DebugData();

   void Render(RBRenderer* pRenderer);

    static const uint32 ms_lines_vertex_capacity = 32768;
    Vertex3D* m_a3DVertexData;

    RBEntityHandle  m_pSphereEntity;
    std::vector<float4> m_spherePositions;
    std::vector<RBColor> m_sphereColors;

    RBEntityHandle  m_pBoxEntity;
    std::vector<RBBVolumeHandle> m_OBBs;
    std::vector<RBColor> m_OBBColors;

    RBHandle<RBGeometry>   m_sp3DLines;
    RBHandle<RBGeometry>   m_spSphere;
    RBHandle<RBGeometry>   m_spBox;

};

static DebugData* ms_pDebugData;

/************************************************************************************************************
 * Constructor
 ************************************************************************************************************/
DebugData::DebugData(RBRenderer* pRenderer)
{
    RBVertexElement pElements[] = {RBVertexElement(0,RBVertexElement::VET_FLOAT3, RBVertexElement::VEU_POSITION),
                                   RBVertexElement(0,RBVertexElement::VET_D3DCOLOR, RBVertexElement::VEU_COLOR)};

    RBEffect* pDebugEffect = RBEffectFactory::GetSingleton()->RetrieveEffect("Debug");
    RBVertexDeclarationPtr spVertexDecl = pRenderer->GetHBMgr()->CreateVertexDeclaration(pElements, 2, pDebugEffect->GetTechnique(1)->GetPass(0));

    m_a3DVertexData = NULL;
    stb_arr_setsize(m_a3DVertexData, ms_lines_vertex_capacity);

    RBVertexBufferPtr spVtxBuffer = pRenderer->GetHBMgr()->CreateVertexBuffer(rb_hw_buffer_usage::HBU_CPUWRITE, sizeof(Vertex3D), 
                                                                                               ms_lines_vertex_capacity, m_a3DVertexData);

    m_sp3DLines = RBGeometry::Create(InvalidHandle, RBTransformComponent::Create(InvalidHandle));
    m_sp3DLines->SetPrimitiveType(RBGeometryPrimitiveType::LINELIST);
    m_sp3DLines->SetVertexData(spVertexDecl, spVtxBuffer, ms_lines_vertex_capacity);

    m_sp3DLines->SetMaterial(new RBMaterial);
    m_sp3DLines->GetMaterial()->SetEffect(pDebugEffect);
    m_sp3DLines->GetMaterial()->SetTechnique(1);

    RBStream* pStream = RBStream::Load("Data/Sphere.dae");
    m_pSphereEntity = pStream->GetObjectAt(0);
    if (m_pSphereEntity != InvalidHandle)
    {
        RBGeometryHandle geometries = RBEntity::Get(m_pSphereEntity).GetComponent<RBGeometry>(0);
        if (geometries != InvalidHandle)
        {
            m_spSphere = geometries;
            m_spSphere->GetMaterial()->SetEffect(pDebugEffect);
            m_spSphere->GetMaterial()->RasterState.IsWireframe = true;
        }
    }

    pStream = RBStream::Load("Data/box.dae");
    m_pBoxEntity = pStream->GetObjectAt(0);
    if (m_pBoxEntity != InvalidHandle)
    {
        RBGeometryHandle geom = RBEntity::Get(m_pBoxEntity).GetComponent<RBGeometry>(0);
        if (geom != InvalidHandle)
        {
            m_spBox = geom;
            m_spBox->GetMaterial()->SetEffect(pDebugEffect);
            m_spBox->GetMaterial()->RasterState.IsWireframe = true;
        }

    }

}

/************************************************************************************************************
 * Destructor
 ************************************************************************************************************/
DebugData::~DebugData()
{
    m_spSphere = NULL;
}


void DebugData::Render(RBRenderer* pRenderer)
{
    int32 vertex_count =  stb_arr_len(m_a3DVertexData);
#if 1
    rb_locked_data pVtx;
    Vertex3D* src_data = m_a3DVertexData;
    while (vertex_count > 0)
    {
        pRenderer->Map(m_sp3DLines->VertexBuffer->GetNative(), rb_hw_buffer_usage::HBM_WRITE_DISCARD, &pVtx);
        uint32 draw_call_vertex_count = (vertex_count > ms_lines_vertex_capacity) ? ms_lines_vertex_capacity : vertex_count;
        uint32 uiSizeToCopy =  draw_call_vertex_count * sizeof(Vertex3D);
        {
            memcpy_s(pVtx.Data, uiSizeToCopy, src_data, uiSizeToCopy);
            RBRenderer::GetSingleton()->Unmap(m_sp3DLines->VertexBuffer->GetNative());
            m_sp3DLines->VertexCount = draw_call_vertex_count;
        }
        pRenderer->Render(m_sp3DLines);

        vertex_count -= ms_lines_vertex_capacity;
        src_data += ms_lines_vertex_capacity;
    }
#endif
    stb_arr_setlen(m_a3DVertexData, 0);

    for (uint32 ui=0; ui < m_spherePositions.size(); ++ui)
    {
        m_spSphere->WorldXForm().Translate = float3(m_spherePositions[ui].x, m_spherePositions[ui].y, m_spherePositions[ui].z);
        m_spSphere->WorldXForm().Scale = m_spherePositions[ui].w;
        m_spSphere->GetMaterial()->Diffuse = m_sphereColors[ui];
        pRenderer->Render(m_spSphere);
    }
    m_spherePositions.clear();
    m_sphereColors.clear();

    for (uint32 ui=0; ui < m_OBBs.size(); ++ui)
    {
        RBBVolume& bv = RBBVolume::Get(m_OBBs[ui]);
        float3x3& rot = m_spBox->WorldXForm().Rotate;
        rot(0,0) = bv.Axes[0].x; rot(0,1) = bv.Axes[1].x; rot(0,2) = bv.Axes[2].x; 
        rot(1,0) = bv.Axes[0].y; rot(1,1) = bv.Axes[1].y; rot(1,2) = bv.Axes[2].y; 
        rot(2,0) = bv.Axes[0].z; rot(2,1) = bv.Axes[1].z; rot(2,2) = bv.Axes[2].z; 

        m_spBox->WorldXForm().Translate = bv.Center;

        m_spBox->WorldXForm().Scale = float3(bv.Extents[0],bv.Extents[1],bv.Extents[2]);
        m_spBox->GetMaterial()->Diffuse = m_OBBColors[ui];
        pRenderer->Render(m_spBox);
    }
    m_OBBs.clear();
    m_OBBColors.clear();
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void Init( RBRenderer* pRenderer )
{
    ms_pDebugData = new DebugData(pRenderer);
}


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void Release()
{
    delete ms_pDebugData;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void Add3DLine(const float3& vStart, const float3& vEnd, const RBColor& color)
{
    Add3DLine(vStart, vEnd, color, color);
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
inline void CopyVertex( Vertex3D& vertex, const float3& pos, const RBColor& color )
{
	vertex.Pos = pos;
    vertex.Color[0] = static_cast<uint8>(color.r*255.0f);
    vertex.Color[1] = static_cast<uint8>(color.g*255.0f);
	vertex.Color[2] = static_cast<uint8>(color.b*255.0f);
    vertex.Color[3] = 255;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void Add3DLine(const float3& vStart, const float3& vEnd, const RBColor& colorStart, const RBColor& colorEnd)
{
    Vertex3D vertex;
    CopyVertex(vertex, vStart, colorStart);
    stb_arr_push(ms_pDebugData->m_a3DVertexData, vertex);
    CopyVertex(vertex, vEnd, colorEnd);
    stb_arr_push(ms_pDebugData->m_a3DVertexData, vertex);
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void Render(RBRenderer* pRenderer)
{
    ms_pDebugData->Render(pRenderer);
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void AddSphere( const float3& vPos, float radius, const RBColor& color )
{
    ms_pDebugData->m_spherePositions.push_back(float4(vPos.x,vPos.y,vPos.z,radius));
    ms_pDebugData->m_sphereColors.push_back(color);
}

static inline void AddRectangle(float3* points, const RBColor& color, uint32 ui0, uint32 ui1, uint32 ui2, uint32 ui3)
{
    RBColor white = RBColor(1,1,1);
    Add3DLine(points[ui0], points[ui1],color, color);
    Add3DLine(points[ui1], points[ui2],color, color);
    Add3DLine(points[ui2], points[ui3],color, color);
    Add3DLine(points[ui3], points[ui0],color, color);

}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void AddFrustum( const RBCamera& camera, const RBColor& color )
{
    float3 cameraUp = camera.GetWorldUp();
    float3 cameraRight = camera.GetWorldRight();
    float3 cameraDir = camera.GetWorldDirection();

    float4x4 projMatrix = RBCamera::BuildProjMatrix(camera);

    RBPlane planes[6];

    RBMath::ComputeFrustumPlanes(planes, projMatrix * camera.GetViewMatrix());

    float3 pointsPersp[] = 
    {
        camera.WorldXForm().Translate + camera.Near() * (cameraDir + camera.Top()    * cameraUp + camera.Left() * cameraRight),
        camera.WorldXForm().Translate + camera.Near() * (cameraDir + camera.Top()    * cameraUp + camera.Right() * cameraRight),
        camera.WorldXForm().Translate + camera.Near() * (cameraDir + camera.Bottom() * cameraUp + camera.Right() * cameraRight),
        camera.WorldXForm().Translate + camera.Near() * (cameraDir + camera.Bottom() * cameraUp + camera.Left() * cameraRight),
        camera.WorldXForm().Translate + camera.Far() * (cameraDir + camera.Top()    * cameraUp + camera.Left() * cameraRight),
        camera.WorldXForm().Translate + camera.Far() * (cameraDir + camera.Top()    * cameraUp + camera.Right() * cameraRight),
        camera.WorldXForm().Translate + camera.Far() * (cameraDir + camera.Bottom() * cameraUp + camera.Right() * cameraRight),
        camera.WorldXForm().Translate + camera.Far() * (cameraDir + camera.Bottom() * cameraUp + camera.Left() * cameraRight)
    };


    float3 pointsOrtho[] = 
    {
        camera.WorldXForm().Translate + camera.Near() * cameraDir + camera.Top()    * cameraUp + camera.Left() * cameraRight,
        camera.WorldXForm().Translate + camera.Near() * cameraDir + camera.Top()    * cameraUp + camera.Right() * cameraRight,
        camera.WorldXForm().Translate + camera.Near() * cameraDir + camera.Bottom() * cameraUp + camera.Right() * cameraRight,
        camera.WorldXForm().Translate + camera.Near() * cameraDir + camera.Bottom() * cameraUp + camera.Left() * cameraRight,
        camera.WorldXForm().Translate + camera.Far() *  cameraDir + camera.Top()    * cameraUp + camera.Left() * cameraRight,
        camera.WorldXForm().Translate + camera.Far() *  cameraDir + camera.Top()    * cameraUp + camera.Right() * cameraRight,
        camera.WorldXForm().Translate + camera.Far() *  cameraDir + camera.Bottom() * cameraUp + camera.Right() * cameraRight,
        camera.WorldXForm().Translate + camera.Far() *  cameraDir + camera.Bottom() * cameraUp + camera.Left() * cameraRight
    };

    float3* points = camera.Ortho() ? pointsOrtho : pointsPersp;

    AddRectangle(points, color,0,1,2,3);
    AddRectangle(points, color,4,5,6,7);
    AddRectangle(points, color,0,4,7,3);
    AddRectangle(points, color,1,5,6,2);
}

void AddOBB( RBBVolumeHandle bv, const RBColor& color )
{
    ms_pDebugData->m_OBBs.push_back(bv);
    ms_pDebugData->m_OBBColors.push_back(color);
}

void add_aabb( float3 center, float3 extents, const RBColor& color )
{
    PROFILE_FUNC();
    float3 points_aabb[] = 
    {
        center + extents * float3(-1.0f, -1.0f, -1.0f),
        center + extents * float3(-1.0f, -1.0f,  1.0f),
        center + extents * float3(-1.0f,  1.0f, -1.0f),
        center + extents * float3(-1.0f,  1.0f,  1.0f),
        center + extents * float3( 1.0f, -1.0f, -1.0f),
        center + extents * float3( 1.0f, -1.0f,  1.0f),
        center + extents * float3( 1.0f,  1.0f, -1.0f),
        center + extents * float3( 1.0f,  1.0f,  1.0f)
    };

    AddRectangle(points_aabb, color,0,1,3,2);
    AddRectangle(points_aabb, color,4,5,7,6);
    AddRectangle(points_aabb, color,0,1,5,4);
    AddRectangle(points_aabb, color,2,3,7,6);
    AddRectangle(points_aabb, color,1,5,7,3);
    AddRectangle(points_aabb, color,0,4,6,2);

}


}

/************************************************************************************************************/
